feat(devframe): proxy-flexible, route-bound WebSocket endpoint#51
Merged
Conversation
✅ Deploy Preview for devfra ready!
To edit notification comments on pull requests, go to your Netlify project configuration. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Why
The dev server already shared one port for HTTP and the RPC WebSocket, but the WS server claimed every upgrade on that port and the SPA discovered it via a server-baked host/port. That breaks two scenarios:
__connection.jsonis no longer reachable.What
Bind the WebSocket to a dedicated route, shared on the web server's port by default
DEVFRAME_WS_ROUTE(__devframe_ws) constant, mounted next to__connection.json.attachWsRpcTransportgainsserver,path, anddestroyUnmatchedoptions. With apathit routes only the matching upgrade pathname and leaves non-matching upgrades for other listeners (so a host's HMR socket keeps working); it returns adetach(). Theserveroption mounts onto an existing HTTP server, sharing its port.startHttpAndWsgainspathandserveroptions — pass an external server to embed the socket inside a host server (devframe never closes a server it doesn't own); when devframe owns the server, off-route upgrades are rejected promptly.Proxy-flexible
__connection.jsonConnectionMeta.websocketnow acceptsnumber | string | { path?, port?, host? }.resolveWsUrl) follows the page's own origin for the relative/path form — resolving against where__connection.jsonloaded and only swappinghttp→ws/https→wss— so the connection survives a proxy that changes host/port/subpath. Explicitport/hostor a fullws(s)://string opt into a fixed cross-origin endpoint (e.g. a side-car).{ websocket: { path: "__devframe_ws" } }; the Vite side-car carries its own{ port, path }since it is genuinely cross-port.Backward compatible: with no
path, the transport keeps its legacy all-upgrades behavior, and numeric/stringwebsocketdescriptors still resolve as before.Tests
resolveWsUrlproxy / cross-origin resolution matrix.This PR was created with the help of an agent.